package service

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"time"

	"strings"

	"math/rand"

	"github.com/uber-go/zap"
	"github.com/valyala/fasthttp"
)

type SyncReturn struct {
	Release map[string]string `json:"release"`
	Config  map[string]string `json:"config"`
}

var oldReturn *SyncReturn
var wg = sync.WaitGroup{}

var c = &fasthttp.Client{}

func startSync() {
	Logger.Info("正在启动，请稍候")

	// 随机等待0-20秒，避免因为同时大量的启动导致对中心服务器的冲击
	tw := rand.Int63n(20e9)
	time.Sleep(time.Duration(tw) * time.Nanosecond)

	url := fmt.Sprintf("http://%s/query/versions", Conf.Client.ServerAddr)
	for {
		defer func() {
			if err := recover(); err != nil {
				Logger.Error("sync error", zap.Error(err.(error)), zap.Stack())
			}
		}()

		appMap, err := json.Marshal(AppMap)
		if err != nil {
			Logger.Warn("apps json encode error", zap.Error(err))
			time.Sleep(Conf.Client.SyncInterval)
			continue
		}

		// ts := time.Now()
		sr, err := sync2Server(url, apps, configs, appMap)
		if err != nil {
			Logger.Warn("sync to server error", zap.Error(err))
			time.Sleep(Conf.Client.SyncInterval)
			continue
		}

		// 重新生成应用启动控制
		for _, app := range AppMap {
			app.needStarted = false
		}

		for app, v := range sr.Release {
			// 如果version为none,则不启动应用
			if strings.TrimSpace(v) != "none" {
				AppMap[app].needStarted = true
			} else {
				sr.Release[app] = "none"
			}
		}

		restartApps := make(map[string]string)
		vers := make(map[string]string)

		// 根据release版本号来下载需要重启的APP
		// 根据config版本号来下载需要的config
		if oldReturn == nil {
			//锁 sr.Release，在appFailed函数中有写入操作
			lock.RLock()
			for k, v := range sr.Release {
				if v != "none" {
					go func(sr *SyncReturn, k string, v string) {
						wg.Add(1)
						defer wg.Done()
						err := downAndMove(sr, k, "", v, 1)
						if err != nil {
							return
						}

						lock.Lock()
						restartApps[k] = v
						lock.Unlock()
					}(sr, k, v)
				}
			}
			lock.RUnlock()

			for k, v := range sr.Config {
				go func(sr *SyncReturn, k string, v string) {
					if strings.TrimSpace(k) == "" {
						return
					}
					wg.Add(1)
					defer wg.Done()

					ss := strings.Split(k, "/")
					var k1, g string
					if len(ss) == 1 {
						k1 = ss[0]
					} else {
						k1 = ss[0]
						g = ss[1]
					}

					lock.RLock()
					v1 := sr.Release[k1]
					lock.RUnlock()

					if v1 != "none" && AppMap[k1].enableConfig {
						err := downAndMove(sr, k1, g, v, 2)
						if err != nil {
							return
						}

						lock.Lock()
						restartApps[k1] = v
						vers[k1] = v
						lock.Unlock()
					}
				}(sr, k, v)
			}

		} else {
			// 对比releases
			// 1.版本变更，则下载最新的，然后重新启动
			// 2.新版本为空，则忽略
			// 3.版本相同，忽略
			for k, vOld := range oldReturn.Release {
				vNew := sr.Release[k]
				if vNew != "none" {
					if vNew == "" {
						continue
					}

					if vOld == vNew {
						continue
					}

					go func(sr *SyncReturn, k string, v string) {
						wg.Add(1)
						defer wg.Done()

						err := downAndMove(sr, k, "", v, 1)
						if err != nil {
							return
						}

						lock.Lock()
						restartApps[k] = v
						lock.Unlock()
					}(sr, k, vNew)
				}
			}

			for k, vOld := range oldReturn.Config {
				vNew := sr.Config[k]
				if vNew == "" {
					continue
				}

				if vOld == vNew {
					continue
				}

				go func(sr *SyncReturn, k string, v string) {
					wg.Add(1)
					defer wg.Done()

					ss := strings.Split(k, "/")
					var k1, g string
					if len(ss) == 1 {
						k1 = ss[0]
					} else {
						k1 = ss[0]
						g = ss[1]
					}

					lock.RLock()
					v1 := sr.Release[k1]
					lock.RUnlock()

					if v1 != "none" && AppMap[k1].enableConfig {
						err := downAndMove(sr, k1, g, v, 2)
						if err != nil {
							return
						}

						lock.Lock()
						restartApps[k1] = v
						vers[k1] = v
						lock.Unlock()

					}
				}(sr, k, vNew)
			}

		}

		// 给waitgroup的goroutines一点启动时间
		time.Sleep(time.Second)
		wg.Wait()

		//根据 config版本号来过滤腰重启的APP
		for k, v := range restartApps {
			go func(k string, v string) {
				wg.Add(1)
				defer wg.Done()

				err = release(k, v, vers[k], sr)
				if err != nil {
					appFailed(sr, k)
					Logger.Debug("启动应用失败", zap.String("当前启动应用", k), zap.String("待启动应用版本", sr.Release[k]),
						zap.String("待启动配置版本", vers[k]))
				} else {
					Logger.Debug("启动应用成功", zap.String("当前启动应用", k), zap.String("当前应用版本", sr.Release[k]),
						zap.String("当前配置版本", vers[k]))
				}

			}(k, v)
		}

		time.Sleep(time.Second)
		wg.Wait()

		oldReturn = sr
		// Logger.Debug("sync耗时", zap.Int64("eclapsed(毫秒)", time.Now().Sub(ts).Nanoseconds()/1000000))
		time.Sleep(Conf.Client.SyncInterval)
	}
}

func sync2Server(url string, apps string, configs string, appMap []byte) (*SyncReturn, error) {
	var body []byte
	var err error

	var arg fasthttp.Args
	arg.Set("apps", apps)
	arg.Set("configs", configs)
	arg.SetBytesV("appmap", appMap)
	arg.Set("ip", ip)
	arg.Set("host", host)

	code, r, err := c.Post(nil, url, &arg)

	if code != 200 || err != nil {
		return nil, fmt.Errorf("请求taitan-server错误,code:%d,error:%v", code, err)
	}

	sr := &SyncReturn{}
	err = json.Unmarshal(r, &sr)
	if err != nil {
		Logger.Warn("sync to server,json decode error", zap.Error(err), zap.String("body", string(body)))
		return nil, err
	}
	return sr, nil
}
func release(k string, v string, confV string, sr *SyncReturn) error {
	v = strings.TrimSpace(v)

	app := AppMap[k]
	// 停止旧应用
	stopApp(app, Conf.Client.AppStopWait)

	tn := v + ".zip"
	uzn := app.mainDir + "/" + tn
	err := unzip(sr, k, uzn, app.mainDir)
	if err != nil {
		appFailed(sr, k)
		return err
	}

	err = delzip(app.mainDir)
	if err != nil {
		appFailed(sr, k)
		return err
	}
	// 启动应用
	// in := appMap[k].MainDir + "/" + k + "--" + v
	err = startApp(app, Conf.Client.AppStartWait)
	if err != nil {
		appFailed(sr, k)
		return err
	}

	app.ConfVer = confV
	app.RelVer = v
	return nil
}

// 先进行Kill然后等待AppStopWait的时间，然后直接Kill -9 防止应用假死导致没有关闭
func stopApp(app *App, st time.Duration) {
	if app.appType == `none` {
		return
	}

	c := fmt.Sprintf("kill `ps -ef | grep %s | grep -v 'grep ' | awk '{print $2}'`", app.Name)
	cmd := exec.Command("/bin/sh", "-c", c)
	cmd.CombinedOutput()

	time.Sleep(st)

	c = fmt.Sprintf("kill -9 `ps -ef | grep %s | grep -v 'grep ' | awk '{print $2}'`", app.Name)
	cmd = exec.Command("/bin/sh", "-c", c)
	cmd.CombinedOutput()

	app.IsStarted = false
}

func downAndMove(sr *SyncReturn, k string, g string, v string, tp int) error {
	app := AppMap[k]

	v = strings.TrimSpace(v)

	var url string
	switch tp {
	case 1:
		url = fmt.Sprintf("http://%s/resources/%s/releases/%s.zip",
			Conf.Client.ServerAddr, k, v)
	case 2:
		url = fmt.Sprintf("http://%s/resources/%s/configs/%s/%s.zip",
			Conf.Client.ServerAddr, k, g, v)
	}

	in := bytes.NewBuffer(nil)
	cmd := exec.Command("/bin/bash")
	cmd.Stdin = in
	in.WriteString("wget " + url)
	bs, err := cmd.CombinedOutput()

	if err != nil {
		Logger.Warn("get releases error", zap.String("app", k), zap.String("url", url),
			zap.String("old_ver", ""), zap.Error(err), zap.String("stdout", string(bs)))
		appFailed(sr, k)
		return err
	}

	var path string
	var name string
	switch tp {
	case 1:
		path = app.mainDir
		name = v + ".zip"
	case 2:
		path = app.configDir
		name = v + ".zip"
	}

	// 判断目录是否存在，不存在则创建
	if !PathExists(path) {
		os.MkdirAll(path, os.ModePerm)
	}

	in = bytes.NewBuffer(nil)
	cmd = exec.Command("/bin/bash")
	cmd.Stdin = in

	s := fmt.Sprintf("mv ./%s %s/%s", name, path, name)
	in.WriteString(s)
	bs, err = cmd.CombinedOutput()

	if err != nil {
		Logger.Warn("move releases error", zap.String("app", k),
			zap.String("old_ver", ""), zap.Error(err), zap.String("stdout", string(bs)))
		appFailed(sr, k)
		return err
	}

	// 解压config
	if tp == 2 {
		tn := v + ".zip"

		uzn := path + "/" + tn
		err := unzip(sr, k, uzn, path)
		if err != nil {
			appFailed(sr, k)
			return err
		}
		err = delExactZip(uzn)
		if err != nil {
			appFailed(sr, k)
			return err
		}
	}
	return nil
}

func PathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

func unzip(sr *SyncReturn, k string, file string, path string) error {
	n := 0
	var err1 error
	for {
		if n >= 3 {
			return err1
		}
		in := bytes.NewBuffer(nil)
		cmd := exec.Command("/bin/bash")
		cmd.Stdin = in

		s := fmt.Sprintf("unzip -o %s -d %s", file, path)
		in.WriteString(s)
		bs, err := cmd.CombinedOutput()
		if err != nil {
			Logger.Warn("unzip releases error", zap.String("app", k),
				zap.String("old_ver", ""), zap.Error(err), zap.String("stdout", string(bs)))
			appFailed(sr, k)

			n++
			err1 = err

			time.Sleep(2 * time.Second)
			continue
		}
		return nil
	}

}

func delzip(path string) error {
	in := bytes.NewBuffer(nil)
	cmd := exec.Command("/bin/bash")
	cmd.Stdin = in

	s := fmt.Sprintf("rm -f %s/*.zip", path)
	in.WriteString(s)
	bs, err := cmd.CombinedOutput()
	if err != nil {
		Logger.Warn("delzip releases error", zap.String("app", path),
			zap.String("old_ver", ""), zap.Error(err), zap.String("stdout", string(bs)))
	}

	return err
}

func delExactZip(path string) error {
	in := bytes.NewBuffer(nil)
	cmd := exec.Command("/bin/bash")
	cmd.Stdin = in

	s := fmt.Sprintf("rm -f %s", path)
	in.WriteString(s)
	bs, err := cmd.CombinedOutput()
	if err != nil {
		Logger.Warn("delzip releases error", zap.String("app", path),
			zap.String("old_ver", ""), zap.Error(err), zap.String("stdout", string(bs)))
	}
	return err
}

func startApp(app *App, sw time.Duration) error {
	if app.appType == "none" {
		return nil
	}

	if app.inStarting {
		Logger.Info("去启动正在启动的应用", zap.String("app", app.Name))
		return nil
	}

	app.inStarting = true
	defer func() {
		app.inStarting = false
	}()

	var cmdStr string
	var appStr string
	switch strings.TrimSpace(app.appType) {
	case "go":
		appStr = app.Name
		cmdStr = fmt.Sprintf(app.initArgs, appStr)
	case "java":
		// 获取starDir底下包含了app.Name的可执行文件
		filepath.Walk(app.startDir, func(path string, f os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if !f.IsDir() && strings.HasPrefix(f.Name(), app.Name) && strings.HasSuffix(f.Name(), ".jar") {
				appStr = f.Name()
			}
			return nil
		})

		if appStr == "" {
			Logger.Warn("找不到可执行文件", zap.String("app", app.Name), zap.String("type", app.appType))
			return errors.New("找不到可执行文件")
		}
		cmdStr = fmt.Sprintf(app.initArgs, appStr)
	case "none":
		cmdStr = app.initArgs
	}

	fmt.Println(cmdStr)
	cmd := exec.Command("/bin/sh", "-c", cmdStr)
	cmd.Dir = app.startDir
	bs, err := cmd.CombinedOutput()
	if err != nil {
		Logger.Warn("start releases error", zap.String("app", app.mainDir),
			zap.String("old_ver", ""), zap.Error(err), zap.String("stdout", string(bs)))
		// 启动失败，说明存在严重bug，重新下载
		if oldReturn != nil {
			oldReturn.Release[app.Name] = ""
			oldReturn.Config[app.Group] = ""
		}
		return err
	}

	time.Sleep(sw)

	// 查询是否启动成功

	c := fmt.Sprintf("ps -ef | grep %s | grep -v 'grep ' | awk '{print $2}'", app.Name)
	cmd = exec.Command("/bin/sh", "-c", c)

	bs, err = cmd.CombinedOutput()
	if err != nil || string(bs) == "" {
		Logger.Warn("应用启动失败", zap.String("app", app.Name), zap.Error(err), zap.String("stdout", string(bs)))
		return err
	}

	app.IsStarted = true
	app.LastStartTime = time.Now()

	Logger.Info("应用启动成功", zap.String("应用名", app.Name), zap.String("应用路径", app.mainDir), zap.String("应用PID", string(bs)))
	return nil
}

func appFailed(sr *SyncReturn, k string) {
	lock.Lock()
	sr.Release[k] = ""
	AppMap[k].IsStarted = false
	lock.Unlock()
}
